package edu.bu.cs511.p5;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * This is the object which controls the concurrency and threads of
 * the system.  It decides if more threads should be let into the
 * system or not.  All schedulers publish their capabilities in terms
 * of concurrency through the childConcurrencyLevel method and this
 * class sits above the root scheduler receiving these notification
 * and sending out threads into the system if it has requested and can
 * handle it.  <p/>
 * 
 * This class follows the Singleton design pattern.
 * <p/>
 * 2/22/06, Gabriel Parmer: Initial go, executing the root scheduler only when
 * it has the concurrency for it.  Using threadpools for concurrent executions.
 * <p/>
 * 4/10/06, Gabriel Parmer: change to singleton.
 */
public class ThreadManager extends SchedTask implements Runnable {
    /** the thread pool */
    private ThreadPoolExecutor pool;

    private static final int CORE_POOL = 10;
    private static final int MAX_POOL = 20;
    private static final int DECREASE_TIME = 1;

    /** the root scheduler */
    private Task root = null;

    private static ThreadManager singleton = null;

    /**
     * This is used to ensure that no more threads are requested, than there is
     * requests for that concurrency.
     */
    private final Object concurrencyLock;

    /** the level of concurrency the root can maintain */
    private int desiredConcurrency = -1;
    private int currentConcurrency = 0;

    /**
     * @REQUIRES root is the root scheduler.
     * <p/>
     * @MODIFIES records the root scheduler and creates all data structures
     * internal to this class.
     */
    private ThreadManager(Task root) {
        super(null);

        setRootScheduler(root);
        concurrencyLock = new Object();

        LinkedBlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>();
        pool = new ThreadPoolExecutor(CORE_POOL, MAX_POOL, DECREASE_TIME, TimeUnit.SECONDS, workQueue);
    }

    /**
     * Factory method of this class that will return the ThreadManager
     * object initialized such that the root scheduler is set to the
     * argument passed in.<p/>
     *
     * @REQUIRES root is the root scheduler.
     * <p/>
     * @MODIFIES if a threadManager has not been created, create one and set the
     * singleton private variable to it.
     * <p/>
     * @EFFECTS return either the newly created ThreadManager object, or
     * the one that was preexisting.
     */
    public static ThreadManager getInstance(Task root) {
        if (singleton == null) {
            singleton = new ThreadManager(root);
        }

        return singleton;
    }

    /**
     * Set the root scheduler to that passed in as an argument.<p/>
     *
     * @REQURIES: the root scheduler, or single opcode task.
     * <p/>
     * @MODIFIES this.root
     */
    public void setRootScheduler(Task root) {
        this.root = root;
    }

    /**
     * Receive requests and concurrency levels from the root scheduler
     * (which, in turn, received them from its children, etc
     * recursivly).  When the concurrency increases so as to request
     * more threads, attempt to spawn them.<p/>
     *
     * @REQURIES: child must be the root scheduler.  concurrency is its
     * available or desired level of concurrency.
     * <p/>
     * @MODIFIES this.desiredConcurrency, and wake up a thread if
     * appropriate.
     */
    protected void childConcurrencyLevel(Task child, int concurrency) {
        if (!child.equals(root)) {
            System.out.println("BUG: threadmanager got notification from a non-root edu.bu.cs511.p5.Task.");
            return;
        }

        desiredConcurrency = concurrency;

        // ready to _try_ and spawn more threads
        synchronized (concurrencyLock) {
            concurrencyLock.notify();
        }
    }

    /** 
     * Not defined to be purposeful.  Delete the entire scheduling
     * hierarchy, and drop references to thid object if you wish to
     * delete it.
     */
    public void endTask() {
    }

    /**
     * Terminate a child of this task, which can only be the root
     * scheduler.<p/>
     *
     * @REQUIRES child must be the root scheduler.<p/>
     *
     * @MODIFIES Delete the root scheduler.
     */
    public void terminateChild(Task child) {
        endTask();
    }

    /**
     * Start executing the system.  While more threads are requested,
     * feed them in.  This is the boot-strap function that begins
     * sending threads into the system based on request and demand.<p/>
     *
     * @MODIFIES starts up the thread pool and waits for concurrency
     * requests so that it can spawn threads and send them into the
     * system.
     */
    public boolean executeTask() {
        while (true) {
            // while more concurrency is requested than is received,
            // keep adding requests (that may queue depending on
            // poolsize).
            while (currentConcurrency >= desiredConcurrency) {
                boolean waitTaken = false;

                while (!waitTaken) {
                    try {
                        // This is where we wait for the level of concurrency to go down or the level of
                        // desired concurrency to go up.
                        synchronized (concurrencyLock) {
                            concurrencyLock.wait();
                        }
                        waitTaken = true;
                    } catch (InterruptedException ie) {
                        System.out.println(ie.getMessage());
                    }
                }
            }

            currentConcurrency++;
            //System.out.println("Executing task");
            pool.execute(this);
        }
    }

    /**
     * This is in the context of a new thread that will be sent into
     * the system through the root scheduler.  Begin executing through
     * the hierarchy.  Used internally when the thread pool begins
     * executing a thread of control.<p/>
     *
     * @MODIFIES This is the starting method whereby a new thread is
     * released into the system.
     */
    public void run() {
        root.executeTask();
        currentConcurrency--;

        // Ready to try and spawn more threads.
        synchronized (concurrencyLock) {
            concurrencyLock.notify();
        }
    }
}
